package hello;

import java.util.List;

import org.hibernate.CacheMode;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.proxy.HibernateProxyHelper;

import persistence.HibernateUtil;
import auction.chapter05.model.BankAccount_Join;
import auction.chapter05.model.BankAccount_Join2Table;
import auction.chapter05.model.BillingDetails_Join2Table;
import auction.chapter05.model.CreditCard_Join2Table;
import auction.chapter05.model.User_Join;
import auction.model.Bid;
import auction.model.Image;
import auction.model.Item;
import auction.model.User;

/**
 * Chapter 13
 * 
 * <many-to-one
   lazy:  "proxy" | no-proxy | false
   fetch: "select" | join

<map
  lazy: "true" | extra | false
  fetch:"select" | join | subselect
 * 
 * The fetch strategy defined in the mapping document affects:
   -retrieval via get() or load()
   -retrieval that happens implicitly when an association is navigated
   -Criteria queries
   -HQL queries if subselect fetching is used. Ignore fetch="join", Have to use HQL left join fetch
 * 
 * @author wei
 *
 */
public class HelloWorldFetchingAndCaching
{
	
	public static void main(String[] args)throws Exception{
		//testQuery();
		//testProxy();
		//testInterception();
		//testPrefetchInBatch();
		//testPrefetchSubselect();
	    //testEagerFetchWithLazyFalse();
	    //testEagerFetchWithJoin();
	    testJoin2Table();
		
		//testDynamicFetch();
		//test2Collection();
		//test2ManyToOneJoin();
		
		//testHibernate_Initialize();
		
		//testSecondLevelCacheHit();
		//testSecondLevelCacheBulkUpdate();
		//testSecondLevelCacheBatchUpdate();
		
		Son s=new Son();
		Parent p=(Parent)s;
		
		//out(s instanceof Son);
		//out(s instanceof Parent);
		
	}
	
	public static void testSecondLevelCacheBatchUpdate()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        out(s.getCacheMode());
        //enabled second-level cache for Items
        Item i=new Item("iii");
        out("new item:"+i);
        s.save(i);//??? seems it doesn't put in second-level cache
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //??? cache miss
        Item geti=(Item)s2.get(Item.class, i.getId());
        //caching this entity
        out("proxy item:"+geti);
        
        tx2.commit();
        s2.close();
        
        Session s3=HibernateUtil.getSessionFactory().openSession();
        Transaction tx3=s3.beginTransaction();
        //The session will never interact with the cache, except to invalidate cache items when updates occur
        s3.setCacheMode(CacheMode.IGNORE);
        
        Item geti2=(Item)s3.get(Item.class, i.getId());//cache hit
        geti2.setTitle("yi guan miao");//updating cache
		
        tx3.commit();
        s3.close();
        
        
        Session s4=HibernateUtil.getSessionFactory().openSession();
        Transaction tx4=s4.beginTransaction();
		
		Item afterquery=(Item)s4.get(Item.class, i.getId());
		out("afterquery after update:"+afterquery.getTitle());//cache hit
        
        tx4.commit();
        s4.close();
        
        
    }
	
	public static void testSecondLevelCacheBulkUpdate()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        out(s.getCacheMode());
        //enabled second-level cache for Items
        Item i=new Item("iii");
        out("new item:"+i);
        s.save(i);//??? seems it doesn't put in second-level cache
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //??? cache miss
        Item proxyi=(Item)s2.get(Item.class, i.getId());
        //caching this entity
        out("proxy item:"+proxyi);
        
        tx2.commit();
        s2.close();
        
        Session s3=HibernateUtil.getSessionFactory().openSession();
        Transaction tx3=s3.beginTransaction();
        
        /* ======= bulk update bypass any cache */
        Query q=s3.createQuery("update Item i set title = :a");
        q.setString("a","yi guan miao b");
		out(q.executeUpdate());
		
		
		//cache miss, seems bulk update invalid second-level cache
		Item afterquery=(Item)s3.get(Item.class, i.getId());
		out("afterquery after update:"+afterquery.getTitle());//hit db to get new data
        
        tx3.commit();
        s3.close();
        
        
        //out(geti.getBids().iterator().next().getPrice());//already filled
        //out(geti.getBids().iterator().next().getItem());//already filled
    }
	
	public static void testSecondLevelCacheHit()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        //enabled second-level cache for Items
        Item i=new Item("iii");
        out("new item:"+i);
        s.save(i);//??? seems it doesn't put in second-level cache
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //??? cache miss
        //Item proxyi=(Item)s2.get(Item.class, i.getId());
        //out("proxy item:"+proxyi);//caching this entity
        
        s2.createQuery("from Item").list();
        
        tx2.commit();
        s2.close();
        
        Session s3=HibernateUtil.getSessionFactory().openSession();
        Transaction tx3=s3.beginTransaction();
        
        //cache hit
        Item cachedi=(Item)s3.get(Item.class, i.getId());
        out("cached item:"+cachedi);
        out(cachedi.getTitle());
        tx3.commit();
        s3.close();
        
        
        //out(geti.getBids().iterator().next().getPrice());//already filled
        //out(geti.getBids().iterator().next().getItem());//already filled
    }
	
	public static void testHibernate_Initialize()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Item i=new Item("iii");
        i.addBid(new Bid(2.2));
        i.addBid(new Bid(5.5));
        
        
        s.save(i);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //lazy="true" fetch="select"
        //Item geti=(Item)s2.get(Item.class, 1L);//only get item
        //Hibernate.initialize(loadi);//collection in loadi won't initialized
        
        Item proxyi=(Item)s2.load(Item.class, i.getId());
        Hibernate.initialize(proxyi);//only initialize Item, collection in proxyi won't initialized
        //Hibernate.initialize(geti.getBids());//hit db to fill the collection, BUT if bid has collection it won't initialized
        out(proxyi.getBids().size());//hit db to get bids
        
        tx2.commit();
        s2.close();
        
        //out(geti.getBids().iterator().next().getPrice());//already filled
        //out(geti.getBids().iterator().next().getItem());//already filled
    }
	
	public static void test2ManyToOneJoin()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Bid b=new Bid(2.2);
        Item i=new Item("iii");
        User u=new User("wei");
        
        b.setItem(i);
        b.setBidder(u);
        /* */
        s.save(i);
        s.save(u);
        s.save(b);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //two many-to-one fetch="join", FK nullable issue left outer join SQL
        //if FK not-null=true, issue inner join means the records must exist
        s2.get(Bid.class, 1L);
        
        //it ONLY select bid table. See above, HQL ignore fetch="join"
        //List<Bid> l=s2.createQuery("from Bid").list();
        
        tx2.commit();
        s2.close();
    }
	
	public static void test2Collection()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Item i=new Item("iii");
        i.addBid(new Bid(2.2));
        i.addBid(new Bid(5.5));
        
        i.getImages().add(new Image("name1","fname1",1,1));
        i.getImages().add(new Image("name2","fname2",2,2));
        i.getImages().add(new Image("name3","fname3",3,3));
        
        s.save(i);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //two collection fetch="join", it produce Cartesian product
        Item loadi=(Item)s2.get(Item.class, 1L);
        out(loadi.getBids().size());
        
        //fetch="subselect", when first visit them issue another subselect SQL 
        //List<Item> l=s2.createQuery("from Item").list();
        //out(l.get(0).getBids().size());
        //out(l.get(0).getImages().size());
        tx2.commit();
        s2.close();
    }
	
	public static void testJoin2Table()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        BankAccount_Join2Table ba=new BankAccount_Join2Table();
        ba.setBankname("ccb");
        
        CreditCard_Join2Table cc=new CreditCard_Join2Table();
        cc.setNumber("411111");
        
        //s.save(ba);
        //s.save(cc);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //============= fetch="select" =============
        
        /* inner join */
        BankAccount_Join2Table loadba=(BankAccount_Join2Table)s2.get(BankAccount_Join2Table.class, 1L);
        
        /* one select from billingdetails, one from bankaccount */
        //BillingDetails_Join2Table db=(BillingDetails_Join2Table)s2.get(BillingDetails_Join2Table.class, 1L);
        
        tx2.commit();
        s2.close();
    }
	
	/*
	 * The fetch strategy defined in the mapping document affects:
        -retrieval via get() or load()
        -retrieval that happens implicitly when an association is navigated
        -Criteria queries
        -HQL queries if subselect fetching is used. it Ignore fetch="join", Have to use HQL left join fetch
	 */
	public static void testEagerFetchWithJoin()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Item i=new Item("iii");
        Bid b=new Bid(2);
        i.addBid(b);
        
        //s.save(i);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //<many-to-one lazy="proxy" fetch="join">
        //Bid loadb=(Bid)s2.get(Bid.class, 1L);//eager fetch, one left outer join sql select bid and item
        
        /* ============= Interesting!!! when use query, it depending on lazy attribute ========*/
        //<many-to-one lazy="false" fetch="join">
        //List<Bid> l=(List<Bid>)s2.createQuery("from Bid").list();//another immediately select * from item issued
        
        //s2.createCriteria(Bid.class).list();//This join works
        
        //<many-to-one lazy="proxy" fetch="join">
        //List<Bid> l=(List<Bid>)s2.createQuery("from Bid").list();//ONLY select * from bid
        //out(l.get(0).getItem());//hit db to get item
        
        //s2.createCriteria(Bid.class).list();//This join works
        
        /* ======================================================================================== */
        
        //<set lazy="true" fetch="join">
        //Item loadi=(Item)s2.get(Item.class, 1L);//eager fetch, one left outer join sql select item and bids
        
        
        /* ============= Interesting!!! when use query, it depending on lazy attribute ========*/
        //<set lazy="true" fetch="join">
        //List<Item> li=(List<Item>)s2.createQuery("from Item").list();//ONLY select * from item
        //li.get(0).getBids().size();//hit db to get bids
        
        //s2.createCriteria(Item.class).list();//This join works
        
        //<set lazy="false" fetch="join">
        //List<Item> li=(List<Item>)s2.createQuery("from Item").list();//another immediately select * from bids
        //out(li.get(0).getBids().size());//NO db hit
        
        //s2.createCriteria(Item.class).list();//This join works
        
        tx2.commit();
        s2.close();
    }
	
	public static void testDynamicFetch()throws Exception{
        Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Item i=new Item("iii");
        Bid b=new Bid(2);
        i.addBid(b);
        
        //s.save(i);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //<many-to-one lazy="false" fetch="join">, eager fetch, one left outer join sql select bid and item
        //Bid loadb=(Bid)s2.get(Bid.class, 1L);
        
        //<set lazy="true" fetch="select">,all default values. 
        //issue left outer join SQL. this way is more flexible
        //List<Item> l1=s2.createQuery("from "+Item.class.getName()+" i left join fetch i.bids").list();
        //return item with fetched bids
        //out(l1.get(0).getBids().size());
        
        //array 0:Item object; 1: Bid object
        //List<Object[]> l2=s2.createQuery("from "+Item.class.getName()+" i left join i.bids").list();

        //FetchMode.DEFAULT : Default to the setting configured in the mapping file.
        //FetchMode.JOIN : Equivalent to fetch="join".
        //FetchMode.SELECT : Equivalent to fetch="select".
        s2.createCriteria(Item.class).setFetchMode("bids", FetchMode.JOIN).list();
        tx2.commit();
        s2.close();
    }
	
	public static void testEagerFetchWithLazyFalse()throws Exception{
	    Session s=HibernateUtil.getSessionFactory().openSession();
        Transaction tx=s.beginTransaction();
        
        Item i=new Item("iii");
        Bid b=new Bid(2);
        i.addBid(b);
        
        s.save(i);
        
        tx.commit();
        s.close();
        
        Session s2=HibernateUtil.getSessionFactory().openSession();
        Transaction tx2=s2.beginTransaction();
        
        //<many-to-one lazy="false">, eager fetch, one sql select bid, one sql select item
        //Bid loadb=(Bid)s2.get(Bid.class, 1L);
        
        //<set lazy="false">,eager fetch, one sql select item, one sql select bids
        Item loadi=(Item)s2.get(Item.class, 1L);
        
        tx2.commit();
        s2.close();
    }
	
	public static void testPrefetchSubselect()throws Exception{
		Session s=HibernateUtil.getSessionFactory().openSession();
		Transaction tx=s.beginTransaction();
		
		int itemCount=9;
		for(int i=0;i<itemCount;i++){
			Item it=new Item("i"+i);
			Bid bid=new Bid(1);
			it.addBid(bid);
			//s.save(it);
		}
		
		
		tx.commit();
		s.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		/* =============== Create Query =========================================== */
		//List<Item> allItem=s2.createQuery("from Item").list();
		
		/* ============== Default without batch prefetching <set batch-size=1, fetch default to select ============= */
		//out(allItem.get(0).getBids().size());//hit db, select * from bids where itemid=?
		//out(allItem.get(1).getBids().size());//hit db, select * from bids where itemid=?
		
		/* ============== Default without batch prefetching <set batch-size=1 fetch=subselect ============= */
		//out(allItem.get(0).getBids().size());//hit db load ALL bid, select * from bids where itemid in (select itemid from items)
		//out(allItem.get(1).getBids().size());//no hit db
		
		
		/* =============== Get =========================================== */
		Item loadi=(Item)s2.get(Item.class, 1L);
		/* ============== Default without batch prefetching <set batch-size=1 fetch=subselect ============= */
        out(loadi.getBids().size());//hit db to load all bids of this item. select * from bids where itemid=this_iid
		
		tx2.commit();
		s2.close();
	}
	
	public static void testPrefetchInBatch()throws Exception{
		Session s=HibernateUtil.getSessionFactory().openSession();
		Transaction tx=s.beginTransaction();
		
		int itemCount=9;
		for(int i=0;i<itemCount;i++){
			Item it=new Item("i"+i);
			Bid bid=new Bid(4);
			it.addBid(bid);
			//s.save(it);
		}
		
		
		tx.commit();
		s.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		/* ==================== many to one proxy ==================================== */
		//List<Bid> allBid=s2.createQuery("from Bid").list();
		//item <class batch-size=10, load one proxy cause at most 10 other proxy initialized
		//allBid.get(0).getItem().getTitle();
		
		/*
		//item <class batch-size=2, load one proxy cause at most 2 other proxy initialized
		out(allBid.get(0).getItem().getTitle());
		out(allBid.get(1).getItem().getTitle());//this line doesn't trigger db hit
		out(allBid.get(2).getItem().getTitle());//issue another sql to load 2 proxy
		out(allBid.get(3).getItem().getTitle());
		out(allBid.get(4).getItem().getTitle());//issue sql to load 1 proxy
		*/
		
		//item <class batch-size=25
		//out(allBid.get(0).getItem().getTitle());
		
		/*====================== one to many proxy collection ==================== */
		
		List<Item> allItem=s2.createQuery("from Item").list();
		//<set batch-size=5
		out(allItem.get(0).getBids().size());//initialize bids collection,load any other 5 collection
		//out(allItem.get(1).getBids().size());//may hit db OR not
		
		tx2.commit();
		s2.close();
	}
	
	public static void testQuery()throws Exception{
		Session s=HibernateUtil.getSessionFactory().openSession();
		Transaction tx=s.beginTransaction();
		
		User_Join u=new User_Join();
		u.setName("ww");
		BankAccount_Join ba=new BankAccount_Join();
		ba.setOwner("wei");
		u.setDefaultBillingDetails(ba);
		
		s.save(ba);
		s.save(u);
		
		tx.commit();
		s.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		/*//HQL
		Query q=s2.createQuery("from User_Join as u where u.name= :aa");
		q.setString("aa", "ww");
		List result=q.list();
		*/
		
		/*
		//query by criteria QBC
		Criteria c=s2.createCriteria(User_Join.class);
		c.add(Restrictions.like("name", "xx"));
		List result=c.list();
		*/
		
		/*
		//query by detached criteria
		DetachedCriteria dc=DetachedCriteria.forClass(User_Join.class);
		dc.add(Restrictions.like("name", "ww"));
		Criteria c2=dc.getExecutableCriteria(s2);
		List result2=c2.list();
		*/
		
		/*
		//query by example QBE
		Criteria c=s2.createCriteria(User_Join.class);
		User_Join example=new User_Join();
		example.setName("ww");
		User_Join example2=new User_Join();
		example2.setName("ww2");
		c.add(Restrictions.or(Example.create(example), Example.create(example2)));
		List result=c.list();
		out(result.size());
		*/
		
		/* //in Chapter 15,QBE excluded entity type, so this doesn't work
		//find the user who's bank name is 123, DO NOT works
		//seems hibernate only consider example3
		User_Join example3=new User_Join();
		example3.setName("not exsit");
		BankAccount_Join bae=new BankAccount_Join();
		bae.setBankname("123");
		example3.setDefaultBillingDetails(bae);
		
		Criteria c2=s2.createCriteria(User_Join.class);
		c2.add(Example.create(example3));
		List result2=c2.list();
		out(result2.size());
		*/
		
		tx2.commit();
		s2.close();
	}
	
	public static void testProxy()throws Exception{
		Session s=HibernateUtil.getSessionFactory().openSession();
		Transaction tx=s.beginTransaction();
		
		Item i=new Item();
		i.setTitle("iiii");
		
		Bid b1=new Bid(4);
		Bid b2=new Bid(5);
		i.addBid(b1);
		i.addBid(b2);
		
		s.save(i);
		
		
		
		tx.commit();
		s.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		Item loadi=(Item)s2.load(Item.class, 1L);
		out(HibernateProxyHelper.getClassWithoutInitializingProxy(loadi));
		out(loadi.getClass());
		getParent(loadi.getClass());
		
		out(loadi instanceof Item);
		
		//out(loadi.getBids().size());//only hit once
		//out(loadi.getBids().isEmpty());//only hit once
		//out(loadi.getBids().contains(new Bid()));//hit db on every call,it's reasonable
		
		tx2.commit();
		s2.close();
	}
	
	public static void testInterception()throws Exception{
		Session s=HibernateUtil.getSessionFactory().openSession();
		Transaction tx=s.beginTransaction();
		
		Message m1=new Message("m1");
		Message m2=new Message("m2");
		m1.setNextMessage(m2);
		
		s.save(m1);
		s.save(m2);
		
		tx.commit();
		s.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		//<property name="text" lazy="true">
		//without instrument, hibernate will select text column
		//with instrument, hibernate won't select those lazy column
		Message loadm=(Message)s2.get(Message.class, 1L);
		
		out("Starting sleep...");
		Thread.currentThread().sleep(5000);
		//without instrument, no DB hit
		//with instrument, Hit DB to get lazy property
		//SEE comment in Message
		//out(loadm.getText());
		
		//<many-to-one name="nextMessage" lazy="no-proxy"> output hello.Message, it isn't proxy now
		out(loadm.getNextMessage().getClass().getName());
		
		
		
		tx2.commit();
		s2.close();
	}
	
	public static void getParent(Class clazz)
	{
		Class parent=clazz.getSuperclass();
	  if(parent !=null)
	  {
			System.out.println("###"+parent.getName());
			getParent(parent);
		}
	}
	
	static void out(Object obj)
	{
		System.out.println("--->" + obj);
	}
}

class Parent{
	
}

class Son extends Parent{
	
}